﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Prism.Common.ApplicationEvents;
using Prism.Common.Interfaces;
using Microsoft.Practices.Prism.PubSubEvents;
using Mitosoft.Definitions.Data;
using Mitosoft.TextFileReader;

namespace ChannelNamesParsingModule.Service
{
	/// <summary>
	/// Responsible for evaluating the contents of log file agains allowed channel names.
	/// </summary>
	// ReSharper disable ClassNeverInstantiated.Global
	public class EvaluateLogChannelsService : IEvaluateLogChannelsService
	// ReSharper restore ClassNeverInstantiated.Global
	{
		#region fields

		/// <summary>
		/// Represents <see cref="ChannelNamesFile"/>
		/// </summary>
		protected string _channelNamesFile;

		/// <summary>
		/// Size of cache used to store line contents.
		/// </summary>
		protected const uint LINE_CONTENT_CACHE_SIZE = 1024;

		/// <summary>
		/// Size of cache used to store line positions.
		/// </summary>
		protected const uint LINE_POSITIONS_CACHE_SIZE = 1024;

		/// <summary>
		/// List containing the allowed log channel names.
		/// </summary>
		protected readonly List<LineContent> _channelNames = new List<LineContent>();

		/// <summary>
		/// Represents the event aggregator.
		/// </summary>
		protected readonly IEventAggregator _eventAggregator;

		/// <summary>
		/// Action responsible to interpret the results.
		/// </summary>
		protected Action<Dictionary<string, List<ulong>>> _evaluationFinishedAction;

		/// <summary>
		/// List containing the evaluation results.
		/// </summary>
		protected readonly Dictionary<string,List<ulong>> _evaluationResults = new Dictionary<string, List<ulong>>();

		/// <summary>
		/// Represents the Id pointing to the log file which should be evaluated.
		/// </summary>
		protected uint? _currentLogLineId;

		protected readonly string _genericDataBlockItemEventRequestingId = Guid.NewGuid().ToString();
		
		#endregion

		#region constructor(s)

		/// <summary>
		/// Creates a new <see cref="EvaluateLogChannelsService"/> instance.
		/// </summary>
		/// <param name="eventAggregator">Represents the event aggregator.</param>
		public EvaluateLogChannelsService(IEventAggregator eventAggregator)
		{
			_eventAggregator = eventAggregator;

			_eventAggregator.GetEvent<GenericDataBlockItemEvent>().Subscribe(
			OnGenericDataBlockItemEvent, ThreadOption.BackgroundThread);
		}

		#endregion

		#region properties

		/// <summary>
		/// Path and name of the file containing the allowed log channels which should be used in log files.
		/// </summary>
		public string ChannelNamesFile
		{
			get
			{
				return _channelNamesFile;
			}
			set
			{
				_channelNamesFile = value;
				_channelNames.Clear();

				if (string.IsNullOrEmpty(value) || !File.Exists(value))
					return;	// No channel values to be retrieved.

				ICacheableDataBlockProvider<IGenericDataBlockItem<string>, string> textfileReader =
					new TextFileReader(
						value,
						LINE_POSITIONS_CACHE_SIZE,
						LINE_CONTENT_CACHE_SIZE,
            0);

				foreach( IGenericDataBlockItem<string> genericDataBlockItem in textfileReader.Values( 0, textfileReader.TotalNumberOfRecords ) )
					_channelNames.Add( new LineContent( genericDataBlockItem.Value, genericDataBlockItem.Id ) );
			}
		}

		#endregion

		#region methods

		/// <summary>
		/// Starts evaluation.
		/// </summary>
		/// <param name="action">Method which will interpret the results.</param>
		public void Start(Action<Dictionary<string, List<ulong>>> action)
		{
			if (action == null) 
				return;	// Does not make sense to start without action.

		  _evaluationResults.Clear();
		  _currentLogLineId = null;

		  if (!_channelNames.Any())
		  {
			action(
			  new Dictionary<string, List<ulong>>
			  {
				{"No channels available", new List<ulong>()}
			  });

			return;
		  }

		  _evaluationFinishedAction = action;
		  RequestLogLine(true); // Initiate - start evaluation process
		}

		/// <summary>
		/// Requests the next log line.
		/// </summary>
		protected void RequestLogLine(bool evaluationIsRunning)
		{
		  // Create event arguments
		  var arguments =
			new GenericDataBlockItemEventArguments(
			  null, 
			  _genericDataBlockItemEventRequestingId, 
			  null)
			{
			  StartingDataId = _currentLogLineId == null ? 0 : (ulong)(_currentLogLineId), 
			  NumberOfDataItems = 1,
			  EvaluationIsRunning = evaluationIsRunning
			};

		  if (_currentLogLineId == null)
			_currentLogLineId = 1;
		  else
			_currentLogLineId = _currentLogLineId + 1;

		  _eventAggregator.GetEvent<GenericDataBlockItemEvent>().Publish(arguments);
		}

		/// <summary>
		/// A generic data block item event was initiated.
		/// </summary>
		/// <param name="arguments">Arguments used to recognize whether a request is being send and 
		/// to know which data is requested.</param>
		protected void OnGenericDataBlockItemEvent(GenericDataBlockItemEventArguments arguments)
		{
		  if (arguments == null)
			return;	// no information available at all

		  if (string.IsNullOrEmpty(arguments.RequestingId) || string.IsNullOrEmpty(arguments.ProvidingId))
			return;	// expecting an available request Id and an available providing Id.

		  arguments.RequestingId = null;  // important to avoid second event evaluation
		  arguments.ProvidingId = null;   // important to avoid second event evaluation

		  #region extra run to 
		  
		  if ((arguments.DataBlockItems == null || !arguments.DataBlockItems.Any()) && arguments.EvaluationIsRunning)
		  {
			  // We request the next log line expecting that again no data will
			  // be returned but we want to inform that evaluation has finished:
			  this.RequestLogLine(false);
			  return;
		  }

		  #endregion

		  #region evaluation finished

		  if (arguments.DataBlockItems == null || !arguments.DataBlockItems.Any())
		  {
			// No data available. We assume that evaluating the log file is finished:
			_evaluationFinishedAction(_evaluationResults);
			return;
		  }
      
		  #endregion

		  EvaluateLogFileLines(arguments.DataBlockItems);
		  RequestLogLine(true); // Request the next log line
		}

		/// <summary>
		/// Evaluates the given log lines against the allowed log channel names.
		/// </summary>
		/// <param name="logLines">Log lines to be evaluated.</param>
		protected void EvaluateLogFileLines(List<IGenericDataBlockItem<string>> logLines)
		{
		  if (logLines == null || !logLines.Any()) 
			return;   // Nothing to evaluate.

		  if (!_channelNames.Any())
			return;   // Nothing to evaluate.

		  var regex = new Regex("Channel=\"([^\"]*)\"");

		  foreach (IGenericDataBlockItem<string> logLine in logLines)
		  {
			Match match = regex.Match(logLine.Value);

			if (!match.Success)
			  continue; // no channels detected in the log file.

			// match.Value is something like this: Channel="....."
			// We are now only interested for the text between the quotes:
			string channelNameInLogFile = match.Value.Split('"')[1];

			  bool channelNameFound = this._channelNames.Any(allowedChannelName => allowedChannelName.Content == channelNameInLogFile);

			  if (!channelNameFound)
			  {
				  string channelNameResult = 
					string.Format(
					  "Undefined channel name: '{0}'",
					  match.Value);

				  if (_evaluationResults.ContainsKey(channelNameResult))
					_evaluationResults[channelNameResult].Add(logLine.Id);
				  else
				  {
					_evaluationResults.Add(channelNameResult, new List<ulong>{logLine.Id});
				  }
			  }
		  }
		}

		#endregion
	}
}
